package com.etone.smartAudit.security.jwt;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.etone.smartAudit.error.CustomParameterizedException;
import com.etone.smartAudit.utils.Md5Util;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class TokenProvider {

    private final Logger log = LoggerFactory.getLogger(TokenProvider.class);

    private static final String AUTHORITIES_KEY = "auth";

    private String secretKey;

    private long tokenValidityInMilliseconds;

    private long tokenValidityInMillisecondsForRememberMe;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${jwt.expiration:1}")
    private Long adminTokenExpirationTime;

    public final static String SYS_PUBLIC_NAME = "sys_smart_audit";

    @PostConstruct
    public void init() {
        this.secretKey = "asdfasfdasfasfasdfasfdasfasfdasfasdf";

        int secondIn1day = 1000 * 60 * 60 * 24;
        this.tokenValidityInMilliseconds = secondIn1day * 2;
        this.tokenValidityInMillisecondsForRememberMe = secondIn1day * 7;
    }

    public String createToken(Authentication authentication, Boolean rememberMe) {
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        long now = (new Date()).getTime();
        Date validity;
        if (rememberMe) {
            validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
        } else {
            validity = new Date(now + this.tokenValidityInMilliseconds);
        }
        //权限过长，缓存到redis中
//        String authKey = "auth_token:" + RandomUtil.randomString(32) + authentication.getName();
        String account = authentication.getName();
        String authKey = getPermiKey(account);
        stringRedisTemplate.opsForValue().set(authKey, authorities, adminTokenExpirationTime + 2, TimeUnit.SECONDS);

        return Jwts.builder()
                .setSubject(authentication.getName())
//                .claim(AUTHORITIES_KEY, authorities)
                .claim(AUTHORITIES_KEY, authKey)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .setExpiration(validity)
                .compact();
    }

    /***
     * 创建指定的一个token
     * @param key
     * @return
     */
    public String createToken(String key) {
        Date validity = new Date(System.currentTimeMillis() + this.tokenValidityInMillisecondsForRememberMe);
        return Jwts.builder()
                .setSubject(SYS_PUBLIC_NAME)
                .claim(AUTHORITIES_KEY, key)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .setExpiration(validity)
                .compact();
    }

    /***
     * 获取token的名字
     * @param token
     * @return
     */
    public String getNameByToken(String token) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(token).getBody();
            return claims.getSubject();
        } catch (Exception e) {
            return null;
        }
    }

    /***
     * 获取权限key
     * @param account
     * @return
     */
    public String getPermiKey(String account) {
        if (StrUtil.isEmpty(account)) {
            throw new CustomParameterizedException("账号为空");
        }
        String key = "auth_token:" + DigestUtil.md5Hex(account + (account.length() >> 2)) + account;
        return key;
    }

    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
        //从reids中获取缓存权限
        String authorities_str = null;
        String authKey = null;
        try {
            authKey = claims.get(AUTHORITIES_KEY).toString();
            authorities_str = stringRedisTemplate.opsForValue().get(authKey);
        } catch (Exception e) {
            //说明权限已经过期
            log.error("权限过期,登录过期了", e);
            if (authKey != null) {
                stringRedisTemplate.delete(authKey);
            }
        }
        if (authorities_str == null) {
            throw new CustomParameterizedException("权限不足");
        }
        Collection<? extends GrantedAuthority> authorities =
//                Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                Arrays.stream(authorities_str.split(","))
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, "", authorities);
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            log.info("Invalid JWT signature.");
            log.trace("Invalid JWT signature trace: {}", e);
        } catch (MalformedJwtException e) {
            log.info("Invalid JWT token.");
            log.trace("Invalid JWT token trace: {}", e);
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token.");
            log.trace("Expired JWT token trace: {}", e);
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token.");
            log.trace("Unsupported JWT token trace: {}", e);
        } catch (IllegalArgumentException e) {
            log.info("JWT token compact of handler are invalid.");
            log.trace("JWT token compact of handler are invalid trace: {}", e);
        }
        return false;
    }
}
